﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.IO;
using System.Collections;

namespace PO_2011.Filter
{
    class Canny : BitmapFastPixel
    {
        private static int[,] maskx;
        private static int[,] masky;
        private static int[,] edgeDir;
        private static int[,] gradient;


        private static int upperThreshold = 60;	// Gradient strength nessicary to start edge
        private static int lowerThreshold = 30;	// Minimum gradient strength to continue edge

        private static String mask_type;

        //ile jest okolicznych piksel, np. maska size 3, ma z kazdej strony 1 piksel, maska 9, ma 4 z kazdej strony
        private static int minus;

        private static Bitmap copy_image;

        public static Bitmap create(Bitmap image, String maskType, int mask_size)
        {
            old_img = image;
            new_img = new Bitmap(image);
            copy_image = new Bitmap(image);
            edgeDir = new int[image.Height, image.Width];
            gradient = new int[image.Height, image.Width];

            mask_type = maskType;
            minus = (int)(mask_size / 2);

            createMask(ref image, ref mask_size);

            return copy_image;
        }


        private static void createMask(ref Bitmap image, ref int mask_size)
        {
            maskx = new int[mask_size, mask_size];
            masky = new int[mask_size, mask_size];

            switch (mask_type)
            {
                case "gaussian":
                    maskx = new int[5, 5] { { 2, 4, 5, 4, 2 }, { 4, 9, 12, 9, 4 }, { 5, 12, 15, 12, 5 }, { 4, 9, 12, 9, 4 }, { 2, 4, 5, 4, 2 } };
                    makeGaussian(ref image, ref mask_size);
                    break;
                case "sobel":
                    maskx = new int[3, 3] { { -1, 0, 1 }, { -2, 0, 2 }, { -1, 0, 1 } };
                    masky = new int[3, 3] { { 1, 2, 1 }, { 0, 0, 0 }, { -1, -2, -1 } };
                    makeSobel(ref image, ref mask_size);
                    cannyPart3();
                    cannyPart4();
                    break;
                default: // general
                    break;
            }
        }

        private static void makeGaussian(ref Bitmap image, ref  int mask_size)
        {
            int[] s; 
            int grey;

            for (int x = 1; x < image.Width - 1; x++)
            {
                for (int y = 1; y < image.Height - 1; y++)
                {
                    // index idzie pionowo po zdjeciu, np. 1, 101, 201, 2, 102, 202, ...
                    s = createColorForEdges(image, x, y, mask_size);

                    grey = (Math.Abs(s[0]) + Math.Abs(s[1])) / 159;

                    if (grey > 255) grey = 255;
                    else if (grey < 0) grey = 0;

                    copy_image.SetPixel(x, y, Color.FromArgb(grey, grey, grey));
                }
            }
        }

        private static void makeSobel(ref Bitmap image, ref  int mask_size)
        {
            int[] s;
            int grey;

            for (int row = 1; row < image.Height - 1; row++)
            {
                for (int col = 1; col < image.Width - 1; col++)
                {
                    // index idzie pionowo po zdjeciu, np. 1, 101, 201, 2, 102, 202, ...
                    s = createColorForEdges(image, col, row, mask_size, true);

                    grey = (Math.Abs(s[0]) + Math.Abs(s[1]));
                    if (grey > 255) grey = 255;
                    else if (grey < 0) grey = 0;

                    double thisAngle = (Math.Atan2(s[0], s[1]) / 3.14159) * 180.0;		// Calculate actual direction of edge
                    int newAngle = 0;

                    /* Convert actual edge direction to approximate value */
                    if (((thisAngle < 22.5) && (thisAngle > -22.5)) || (thisAngle > 157.5) || (thisAngle < -157.5))
                        newAngle = 0;
                    if (((thisAngle > 22.5) && (thisAngle < 67.5)) || ((thisAngle < -112.5) && (thisAngle > -157.5)))
                        newAngle = 45;
                    if (((thisAngle > 67.5) && (thisAngle < 112.5)) || ((thisAngle < -67.5) && (thisAngle > -112.5)))
                        newAngle = 90;
                    if (((thisAngle > 112.5) && (thisAngle < 157.5)) || ((thisAngle < -22.5) && (thisAngle > -67.5)))
                        newAngle = 135;

                    edgeDir[row,col] = newAngle;	

                    copy_image.SetPixel(col, row, Color.FromArgb(grey, grey, grey));
                    gradient[row, col] = grey;
                }
            }
            
        }

        private static void cannyPart3()
        {
            for (int row = 1; row < old_img.Height - 1; row++)
            {
                for (int col = 1; col < old_img.Width - 1; col++)
                {
                    if (gradient[row, col] > upperThreshold)
                    {		// Check to see if current pixel has a high enough gradient strength to be part of an edge
                        /* Switch based on current pixel's edge direction */
                        switch (edgeDir[row, col])
                        {
                            case 0:
                                findEdge(0, 1, row, col, 0, lowerThreshold);
                                break;
                            case 45:
                                findEdge(1, 1, row, col, 45, lowerThreshold);
                                break;
                            case 90:
                                findEdge(1, 0, row, col, 90, lowerThreshold);
                                break;
                            case 135:
                                findEdge(1, -1, row, col, 135, lowerThreshold);
                                break;
                            default:
                                copy_image.SetPixel(col, row, Color.FromArgb(0, 0, 0));
                                break;
                        }
                    }
                    else
                    {
                        copy_image.SetPixel(col, row, Color.FromArgb(0, 0, 0));
                    }
                }
            }
        }

        private static int [] createColorForEdges(Bitmap image, int x, int y, int mask_size, bool maskyBool = false)
        {
            int xx = 0, yy = 0;
            int [] grey = new int [2] {0, 0};

            for (int i = 0; i < mask_size; i++)
            {
                for (int j = 0; j < mask_size; j++)
                {
                    xx = x + i - minus;
                    yy = y + j - minus;

                    if (xx >= image.Width | xx < 0 | yy >= image.Height | yy < 0) continue;

                    grey[0] += maskx[j, i] * (int)image.GetPixel(xx, yy).R;
                    if (maskyBool) grey[1] += masky[j, i] * (int)image.GetPixel(xx, yy).R;
                }
            }

            return grey;
        }

        private static void findEdge(int rowShift, int colShift, int row, int col, int dir, int lowerThreshold)
        {
	        int W = old_img.Width;
	        int H = old_img.Height;
	        int newRow = 0;
	        int newCol = 0;
            bool edgeEnd = false;

	        /* Find the row and column values for the next possible pixel on the edge */
	        if (colShift < 0) {
		        if (col > 0)
			        newCol = col + colShift;
		        else
			        edgeEnd = true;
	        } else if (col < W - 1) {
		        newCol = col + colShift;
	        } else
		        edgeEnd = true;		// If the next pixel would be off image, don't do the while loop
	        if (rowShift < 0) {
		        if (row > 0)
			        newRow = row + rowShift;
		        else
			        edgeEnd = true;
	        } else if (row < H - 1) {
		        newRow = row + rowShift;
	        } else
		        edgeEnd = true;	
		
	        /* Determine edge directions and gradient strengths */
	        while ( (edgeDir[newRow,newCol]==dir) && !edgeEnd && (gradient[newRow,newCol] > lowerThreshold) ) {
		        /* Set the new pixel as white to show it is an edge */
                copy_image.SetPixel(newCol, newRow, Color.FromArgb(0, 0, 0));   

                if (colShift < 0) {
			        if (newCol > 0)
				        newCol = newCol + colShift;
			        else
				        edgeEnd = true;	
		        } else if (newCol < W - 1) {
			        newCol = newCol + colShift;
		        } else
			        edgeEnd = true;	
		        if (rowShift < 0) {
			        if (newRow > 0)
				        newRow = newRow + rowShift;
			        else
				        edgeEnd = true;
		        } else if (newRow < H - 1) {
			        newRow = newRow + rowShift;
		        } else
			        edgeEnd = true;	
	        }	
        }



        private static void cannyPart4()
        {
            for (int row = 1; row < old_img.Height - 1; row++)
            {
                for (int col = 1; col < old_img.Width - 1; col++)
                {
                    if (getFastPixel(col, row)[0] == 255)
                    {		// Check to see if current pixel is an edge
                        /* Switch based on current pixel's edge direction */
                        switch (edgeDir[row, col])
                        {
                            case 0:
                                suppressNonMax(1, 0, row, col, 0, lowerThreshold);
                                break;
                            case 45:
                                suppressNonMax(1, -1, row, col, 45, lowerThreshold);
                                break;
                            case 90:
                                suppressNonMax(0, 1, row, col, 90, lowerThreshold);
                                break;
                            case 135:
                                suppressNonMax(1, 1, row, col, 135, lowerThreshold);
                                break;
                            default:
                                break;
                        }
                    }
                }
            }
        }

        private static void suppressNonMax(int rowShift, int colShift, int row, int col, int dir, int lowerThreshold)
        {
            int W = old_img.Width;
            int H = old_img.Height;
	        int newRow = 0;
	        int newCol = 0;
            bool edgeEnd = false;
	        float[,] nonMax = new float[old_img.Width,3];		// Temporarily stores gradients and positions of pixels in parallel edges
	        int pixelCount = 0;		// Stores the number of pixels in parallel edges
	        int count;				
	        int[] max = new int[3];			// Maximum point in a wide edge
	
	        if (colShift < 0) {
		        if (col > 0)
			        newCol = col + colShift;
		        else
			        edgeEnd = true;
	        } else if (col < W - 1) {
		        newCol = col + colShift;
	        } else
		        edgeEnd = true;		// If the next pixel would be off image, don't do the while loop
	        if (rowShift < 0) {
		        if (row > 0)
			        newRow = row + rowShift;
		        else
			        edgeEnd = true;
	        } else if (row < H - 1) {
		        newRow = row + rowShift;
	        } else
		        edgeEnd = true;	
	        //i = (unsigned long)(newRow*3*W + 3*newCol);
	        /* Find non-maximum parallel edges tracing up */
            while ((edgeDir[newRow, newCol] == dir) && !edgeEnd && (getFastPixel(newCol, newRow)[0] == 255))
            {
		        if (colShift < 0) {
			        if (newCol > 0)
				        newCol = newCol + colShift;
			        else
				        edgeEnd = true;	
		        } else if (newCol < W - 1) {
			        newCol = newCol + colShift;
		        } else
			        edgeEnd = true;	
		        if (rowShift < 0) {
			        if (newRow > 0)
				        newRow = newRow + rowShift;
			        else
				        edgeEnd = true;
		        } else if (newRow < H - 1) {
			        newRow = newRow + rowShift;
		        } else
			        edgeEnd = true;	
		        nonMax[pixelCount,0] = newRow;
		        nonMax[pixelCount,1] = newCol;
		        nonMax[pixelCount,2] = gradient[newRow, newCol];
		        pixelCount++;
		        //i = (unsigned long)(newRow*3*W + 3*newCol);
	        }

	        /* Find non-maximum parallel edges tracing down */
	        edgeEnd = false;
	        colShift *= -1;
	        rowShift *= -1;
	        if (colShift < 0) {
		        if (col > 0)
			        newCol = col + colShift;
		        else
			        edgeEnd = true;
	        } else if (col < W - 1) {
		        newCol = col + colShift;
	        } else
		        edgeEnd = true;	
	        if (rowShift < 0) {
		        if (row > 0)
			        newRow = row + rowShift;
		        else
			        edgeEnd = true;
	        } else if (row < H - 1) {
		        newRow = row + rowShift;
	        } else
		        edgeEnd = true;	
	        //i = (unsigned long)(newRow*3*W + 3*newCol);
            while ((edgeDir[newRow, newCol] == dir) && !edgeEnd && (getFastPixel(newCol, newRow)[0] == 255))
            {
		        if (colShift < 0) {
			        if (newCol > 0)
				        newCol = newCol + colShift;
			        else
				        edgeEnd = true;	
		        } else if (newCol < W - 1) {
			        newCol = newCol + colShift;
		        } else
			        edgeEnd = true;	
		        if (rowShift < 0) {
			        if (newRow > 0)
				        newRow = newRow + rowShift;
			        else
				        edgeEnd = true;
		        } else if (newRow < H - 1) {
			        newRow = newRow + rowShift;
		        } else
			        edgeEnd = true;	
		        nonMax[pixelCount,0] = newRow;
		        nonMax[pixelCount,1] = newCol;
		        nonMax[pixelCount,2] = gradient[newRow,newCol];
		        pixelCount++;
		        //i = (unsigned long)(newRow*3*W + 3*newCol);
	        }

	        /* Suppress non-maximum edges */
	        max[0] = 0;
	        max[1] = 0;
	        max[2] = 0;
	        for (count = 0; count < pixelCount; count++) {
		        if (nonMax[count,2] > max[2]) {
			        max[0] = (int)nonMax[count,0];
                    max[1] = (int)nonMax[count, 1];
                    max[2] = (int)nonMax[count, 2];
		        }
	        }
	        for (count = 0; count < pixelCount; count++) {
                copy_image.SetPixel((int)nonMax[count, 1], (int)nonMax[count, 0], Color.FromArgb(0, 0, 0));   
	        }
        }


    }
}
